/*
 * ndis_events - Receive NdisMIndicateStatus() events using WMI
 * Copyright (c) 2004-2006, Jouni Malinen <j@w1.fi>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Alternatively, this software may be distributed under the terms of BSD
 * license.
 *
 * See README and COPYING for more details.
 */

#define _WIN32_WINNT    0x0400

#include "includes.h"

#ifndef COBJMACROS
#define COBJMACROS
#endif /* COBJMACROS */
#include <wbemidl.h>

#include "common.h"


static int wmi_refcnt = 0;
static int wmi_first = 1;

struct ndis_events_data {
        IWbemObjectSink sink;
        IWbemObjectSinkVtbl sink_vtbl;

        IWbemServices *pSvc;
        IWbemLocator *pLoc;

        HANDLE read_pipe, write_pipe, event_avail;
        UINT ref;
        int terminating;
        char *ifname; /* {GUID..} */
        WCHAR *adapter_desc;
};

#define BstrAlloc(x) (x) ? SysAllocString(x) : NULL
#define BstrFree(x) if (x) SysFreeString(x)

/* WBEM / WMI wrapper functions, to perform in-place conversion of WCHARs to
 * BSTRs */
HRESULT STDMETHODCALLTYPE call_IWbemServices_ExecQuery(
        IWbemServices *pSvc, LPCWSTR strQueryLanguage, LPCWSTR strQuery,
        long lFlags, IWbemContext *pCtx, IEnumWbemClassObject **ppEnum)
{
        BSTR bsQueryLanguage, bsQuery;
        HRESULT hr;

        bsQueryLanguage = BstrAlloc(strQueryLanguage);
        bsQuery = BstrAlloc(strQuery);

        hr = IWbemServices_ExecQuery(pSvc, bsQueryLanguage, bsQuery, lFlags,
                                     pCtx, ppEnum);

        BstrFree(bsQueryLanguage);
        BstrFree(bsQuery);

        return hr;
}


HRESULT STDMETHODCALLTYPE call_IWbemServices_ExecNotificationQueryAsync(
        IWbemServices *pSvc, LPCWSTR strQueryLanguage, LPCWSTR strQuery,
        long lFlags, IWbemContext *pCtx, IWbemObjectSink *pResponseHandler)
{
        BSTR bsQueryLanguage, bsQuery;
        HRESULT hr;

        bsQueryLanguage = BstrAlloc(strQueryLanguage);
        bsQuery = BstrAlloc(strQuery);

        hr = IWbemServices_ExecNotificationQueryAsync(pSvc, bsQueryLanguage,
                                                      bsQuery, lFlags, pCtx,
                                                      pResponseHandler);

        BstrFree(bsQueryLanguage);
        BstrFree(bsQuery);

        return hr;
}


HRESULT STDMETHODCALLTYPE call_IWbemLocator_ConnectServer(
        IWbemLocator *pLoc, LPCWSTR strNetworkResource, LPCWSTR strUser,
        LPCWSTR strPassword, LPCWSTR strLocale, long lSecurityFlags,
        LPCWSTR strAuthority, IWbemContext *pCtx, IWbemServices **ppNamespace)
{
        BSTR bsNetworkResource, bsUser, bsPassword, bsLocale, bsAuthority;
        HRESULT hr;

        bsNetworkResource = BstrAlloc(strNetworkResource);
        bsUser = BstrAlloc(strUser);
        bsPassword = BstrAlloc(strPassword);
        bsLocale = BstrAlloc(strLocale);
        bsAuthority = BstrAlloc(strAuthority);

        hr = IWbemLocator_ConnectServer(pLoc, bsNetworkResource, bsUser,
                                        bsPassword, bsLocale, lSecurityFlags,
                                        bsAuthority, pCtx, ppNamespace);

        BstrFree(bsNetworkResource);
        BstrFree(bsUser);
        BstrFree(bsPassword);
        BstrFree(bsLocale);
        BstrFree(bsAuthority);

        return hr;
}


enum event_types { EVENT_CONNECT, EVENT_DISCONNECT, EVENT_MEDIA_SPECIFIC,
                   EVENT_ADAPTER_ARRIVAL, EVENT_ADAPTER_REMOVAL };

static int ndis_events_get_adapter(struct ndis_events_data *events,
                                   const char *ifname, const char *desc);


static int ndis_events_constructor(struct ndis_events_data *events)
{
        events->ref = 1;

        if (!CreatePipe(&events->read_pipe, &events->write_pipe, NULL, 512)) {
                wpa_printf(MSG_ERROR, "CreatePipe() failed: %d",
                           (int) GetLastError());
                return -1;
        }
        events->event_avail = CreateEvent(NULL, TRUE, FALSE, NULL);
        if (events->event_avail == NULL) {
                wpa_printf(MSG_ERROR, "CreateEvent() failed: %d",
                           (int) GetLastError());
                CloseHandle(events->read_pipe);
                CloseHandle(events->write_pipe);
                return -1;
        }

        return 0;
}


static void ndis_events_destructor(struct ndis_events_data *events)
{
        CloseHandle(events->read_pipe);
        CloseHandle(events->write_pipe);
        CloseHandle(events->event_avail);
        IWbemServices_Release(events->pSvc);
        IWbemLocator_Release(events->pLoc);
        if (--wmi_refcnt == 0)
                CoUninitialize();
}


static HRESULT STDMETHODCALLTYPE
ndis_events_query_interface(IWbemObjectSink *this, REFIID riid, void **obj)
{
        *obj = NULL;

        if (IsEqualIID(riid, &IID_IUnknown) ||
            IsEqualIID(riid, &IID_IWbemObjectSink)) {
                *obj = this;
                IWbemObjectSink_AddRef(this);
                return NOERROR;
        }

        return E_NOINTERFACE;
}


static ULONG STDMETHODCALLTYPE ndis_events_add_ref(IWbemObjectSink *this)
{
        struct ndis_events_data *events = (struct ndis_events_data *) this;
        return ++events->ref;
}


static ULONG STDMETHODCALLTYPE ndis_events_release(IWbemObjectSink *this)
{
        struct ndis_events_data *events = (struct ndis_events_data *) this;

        if (--events->ref != 0)
                return events->ref;

        ndis_events_destructor(events);
        wpa_printf(MSG_DEBUG, "ndis_events: terminated");
        os_free(events->adapter_desc);
        os_free(events->ifname);
        os_free(events);
        return 0;
}


static int ndis_events_send_event(struct ndis_events_data *events,
                                  enum event_types type,
                                  char *data, size_t data_len)
{
        char buf[512], *pos, *end;
        int _type;
        DWORD written;

        end = buf + sizeof(buf);
        _type = (int) type;
        os_memcpy(buf, &_type, sizeof(_type));
        pos = buf + sizeof(_type);

        if (data) {
                if (2 + data_len > (size_t) (end - pos)) {
                        wpa_printf(MSG_DEBUG, "Not enough room for send_event "
                                   "data (%d)", data_len);
                        return -1;
                }
                *pos++ = data_len >> 8;
                *pos++ = data_len & 0xff;
                os_memcpy(pos, data, data_len);
                pos += data_len;
        }

        if (WriteFile(events->write_pipe, buf, pos - buf, &written, NULL)) {
                SetEvent(events->event_avail);
                return 0;
        }
        wpa_printf(MSG_INFO, "WriteFile() failed: %d", (int) GetLastError());
        return -1;
}


static void ndis_events_media_connect(struct ndis_events_data *events)
{
        wpa_printf(MSG_DEBUG, "MSNdis_StatusMediaConnect");
        ndis_events_send_event(events, EVENT_CONNECT, NULL, 0);
}


static void ndis_events_media_disconnect(struct ndis_events_data *events)
{
        wpa_printf(MSG_DEBUG, "MSNdis_StatusMediaDisconnect");
        ndis_events_send_event(events, EVENT_DISCONNECT, NULL, 0);
}


static void ndis_events_media_specific(struct ndis_events_data *events,
                                       IWbemClassObject *pObj)
{
        VARIANT vt;
        HRESULT hr;
        LONG lower, upper, k;
        UCHAR ch;
        char *data, *pos;
        size_t data_len;

        wpa_printf(MSG_DEBUG, "MSNdis_StatusMediaSpecificIndication");

        /* This is the StatusBuffer from NdisMIndicateStatus() call */
        hr = IWbemClassObject_Get(pObj, L"NdisStatusMediaSpecificIndication",
                                  0, &vt, NULL, NULL);
        if (FAILED(hr)) {
                wpa_printf(MSG_DEBUG, "Could not get "
                           "NdisStatusMediaSpecificIndication from "
                           "the object?!");
                return;
        }

        SafeArrayGetLBound(V_ARRAY(&vt), 1, &lower);
        SafeArrayGetUBound(V_ARRAY(&vt), 1, &upper);
        data_len = upper - lower + 1;
        data = os_malloc(data_len);
        if (data == NULL) {
                wpa_printf(MSG_DEBUG, "Failed to allocate buffer for event "
                           "data");
                VariantClear(&vt);
                return;
        }

        pos = data;
        for (k = lower; k <= upper; k++) {
                SafeArrayGetElement(V_ARRAY(&vt), &k, &ch);
                *pos++ = ch;
        }
        wpa_hexdump(MSG_DEBUG, "MediaSpecificEvent", (u8 *) data, data_len);

        VariantClear(&vt);

        ndis_events_send_event(events, EVENT_MEDIA_SPECIFIC, data, data_len);

        os_free(data);
}


static void ndis_events_adapter_arrival(struct ndis_events_data *events)
{
        wpa_printf(MSG_DEBUG, "MSNdis_NotifyAdapterArrival");
        ndis_events_send_event(events, EVENT_ADAPTER_ARRIVAL, NULL, 0);
}


static void ndis_events_adapter_removal(struct ndis_events_data *events)
{
        wpa_printf(MSG_DEBUG, "MSNdis_NotifyAdapterRemoval");
        ndis_events_send_event(events, EVENT_ADAPTER_REMOVAL, NULL, 0);
}


static HRESULT STDMETHODCALLTYPE
ndis_events_indicate(IWbemObjectSink *this, long lObjectCount,
                     IWbemClassObject __RPC_FAR *__RPC_FAR *ppObjArray)
{
        struct ndis_events_data *events = (struct ndis_events_data *) this;
        long i;

        if (events->terminating) {
                wpa_printf(MSG_DEBUG, "ndis_events_indicate: Ignore "
                           "indication - terminating");
                return WBEM_NO_ERROR;
        }
        /* wpa_printf(MSG_DEBUG, "Notification received - %d object(s)",
           lObjectCount); */

        for (i = 0; i < lObjectCount; i++) {
                IWbemClassObject *pObj = ppObjArray[i];
                HRESULT hr;
                VARIANT vtClass, vt;

                hr = IWbemClassObject_Get(pObj, L"__CLASS", 0, &vtClass, NULL,
                                          NULL);
                if (FAILED(hr)) {
                        wpa_printf(MSG_DEBUG, "Failed to get __CLASS from "
                                   "event.");
                        break;
                }
                /* wpa_printf(MSG_DEBUG, "CLASS: '%S'", vtClass.bstrVal); */

                hr = IWbemClassObject_Get(pObj, L"InstanceName", 0, &vt, NULL,
                                          NULL);
                if (FAILED(hr)) {
                        wpa_printf(MSG_DEBUG, "Failed to get InstanceName "
                                   "from event.");
                        VariantClear(&vtClass);
                        break;
                }

                if (wcscmp(vtClass.bstrVal,
                           L"MSNdis_NotifyAdapterArrival") == 0) {
                        wpa_printf(MSG_DEBUG, "ndis_events_indicate: Try to "
                                   "update adapter description since it may "
                                   "have changed with new adapter instance");
                        ndis_events_get_adapter(events, events->ifname, NULL);
                }

                if (wcscmp(events->adapter_desc, vt.bstrVal) != 0) {
                        wpa_printf(MSG_DEBUG, "ndis_events_indicate: Ignore "
                                   "indication for foreign adapter: "
                                   "InstanceName: '%S' __CLASS: '%S'",
                                   vt.bstrVal, vtClass.bstrVal);
                        VariantClear(&vtClass);
                        VariantClear(&vt);
                        continue;
                }
                VariantClear(&vt);

                if (wcscmp(vtClass.bstrVal,
                           L"MSNdis_StatusMediaSpecificIndication") == 0) {
                        ndis_events_media_specific(events, pObj);
                } else if (wcscmp(vtClass.bstrVal,
                                  L"MSNdis_StatusMediaConnect") == 0) {
                        ndis_events_media_connect(events);
                } else if (wcscmp(vtClass.bstrVal,
                                  L"MSNdis_StatusMediaDisconnect") == 0) {
                        ndis_events_media_disconnect(events);
                } else if (wcscmp(vtClass.bstrVal,
                                  L"MSNdis_NotifyAdapterArrival") == 0) {
                        ndis_events_adapter_arrival(events);
                } else if (wcscmp(vtClass.bstrVal,
                                  L"MSNdis_NotifyAdapterRemoval") == 0) {
                        ndis_events_adapter_removal(events);
                } else {
                        wpa_printf(MSG_DEBUG, "Unepected event - __CLASS: "
                                   "'%S'", vtClass.bstrVal);
                }

                VariantClear(&vtClass);
        }

        return WBEM_NO_ERROR;
}


static HRESULT STDMETHODCALLTYPE
ndis_events_set_status(IWbemObjectSink *this, long lFlags, HRESULT hResult,
                       BSTR strParam, IWbemClassObject __RPC_FAR *pObjParam)
{
        return WBEM_NO_ERROR;
}


static int notification_query(IWbemObjectSink *pDestSink,
                              IWbemServices *pSvc, const char *class_name)
{
        HRESULT hr;
        WCHAR query[256];

        _snwprintf(query, 256,
                  L"SELECT * FROM %S", class_name);
        wpa_printf(MSG_DEBUG, "ndis_events: WMI: %S", query);
        hr = call_IWbemServices_ExecNotificationQueryAsync(
                pSvc, L"WQL", query, 0, 0, pDestSink);
        if (FAILED(hr)) {
                wpa_printf(MSG_DEBUG, "ExecNotificationQueryAsync for %s "
                           "failed with hresult of 0x%x",
                           class_name, (int) hr);
                return -1;
        }

        return 0;
}


static int register_async_notification(IWbemObjectSink *pDestSink,
                                       IWbemServices *pSvc)
{
        int i;
        const char *class_list[] = {
                "MSNdis_StatusMediaConnect",
                "MSNdis_StatusMediaDisconnect",
                "MSNdis_StatusMediaSpecificIndication",
                "MSNdis_NotifyAdapterArrival",
                "MSNdis_NotifyAdapterRemoval",
                NULL
        };

        for (i = 0; class_list[i]; i++) {
                if (notification_query(pDestSink, pSvc, class_list[i]) < 0)
                        return -1;
        }

        return 0;
}


void ndis_events_deinit(struct ndis_events_data *events)
{
        events->terminating = 1;
        IWbemServices_CancelAsyncCall(events->pSvc, &events->sink);
        IWbemObjectSink_Release(&events->sink);
        /*
         * Rest of deinitialization is done in ndis_events_destructor() once
         * all reference count drops to zero.
         */
}


static int ndis_events_use_desc(struct ndis_events_data *events,
                                const char *desc)
{
        char *tmp, *pos;
        size_t len;

        if (desc == NULL) {
                if (events->adapter_desc == NULL)
                        return -1;
                /* Continue using old description */
                return 0;
        }

        tmp = os_strdup(desc);
        if (tmp == NULL)
                return -1;

        pos = os_strstr(tmp, " (Microsoft's Packet Scheduler)");
        if (pos)
                *pos = '\0';

        len = os_strlen(tmp);
        events->adapter_desc = os_malloc((len + 1) * sizeof(WCHAR));
        if (events->adapter_desc == NULL) {
                os_free(tmp);
                return -1;
        }
        _snwprintf(events->adapter_desc, len + 1, L"%S", tmp);
        os_free(tmp);
        return 0;
}


static int ndis_events_get_adapter(struct ndis_events_data *events,
                                   const char *ifname, const char *desc)
{
        HRESULT hr;
        IWbemServices *pSvc;
#define MAX_QUERY_LEN 256
        WCHAR query[MAX_QUERY_LEN];
        IEnumWbemClassObject *pEnumerator;
        IWbemClassObject *pObj;
        ULONG uReturned;
        VARIANT vt;
        int len, pos;

        /*
         * Try to get adapter descriptor through WMI CIMv2 Win32_NetworkAdapter
         * to have better probability of matching with InstanceName from
         * MSNdis events. If this fails, use the provided description.
         */

        os_free(events->adapter_desc);
        events->adapter_desc = NULL;

        hr = call_IWbemLocator_ConnectServer(
                events->pLoc, L"ROOT\\CIMV2", NULL, NULL, 0, 0, 0, 0, &pSvc);
        if (FAILED(hr)) {
                wpa_printf(MSG_ERROR, "ndis_events: Could not connect to WMI "
                           "server (ROOT\\CIMV2) - error 0x%x", (int) hr);
                return ndis_events_use_desc(events, desc);
        }
        wpa_printf(MSG_DEBUG, "ndis_events: Connected to ROOT\\CIMV2.");

        _snwprintf(query, MAX_QUERY_LEN,
                  L"SELECT Index FROM Win32_NetworkAdapterConfiguration "
                  L"WHERE SettingID='%S'", ifname);
        wpa_printf(MSG_DEBUG, "ndis_events: WMI: %S", query);

        hr = call_IWbemServices_ExecQuery(
                pSvc, L"WQL", query,
                WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
                NULL, &pEnumerator);
        if (!SUCCEEDED(hr)) {
                wpa_printf(MSG_DEBUG, "ndis_events: Failed to query interface "
                           "GUID from Win32_NetworkAdapterConfiguration: "
                           "0x%x", (int) hr);
                IWbemServices_Release(pSvc);
                return ndis_events_use_desc(events, desc);
        }

        uReturned = 0;
        hr = IEnumWbemClassObject_Next(pEnumerator, WBEM_INFINITE, 1,
                                       &pObj, &uReturned);
        if (!SUCCEEDED(hr) || uReturned == 0) {
                wpa_printf(MSG_DEBUG, "ndis_events: Failed to find interface "
                           "GUID from Win32_NetworkAdapterConfiguration: "
                           "0x%x", (int) hr);
                IEnumWbemClassObject_Release(pEnumerator);
                IWbemServices_Release(pSvc);
                return ndis_events_use_desc(events, desc);
        }
        IEnumWbemClassObject_Release(pEnumerator);

        VariantInit(&vt);
        hr = IWbemClassObject_Get(pObj, L"Index", 0, &vt, NULL, NULL);
        if (!SUCCEEDED(hr)) {
                wpa_printf(MSG_DEBUG, "ndis_events: Failed to get Index from "
                           "Win32_NetworkAdapterConfiguration: 0x%x",
                           (int) hr);
                IWbemServices_Release(pSvc);
                return ndis_events_use_desc(events, desc);
        }

        _snwprintf(query, MAX_QUERY_LEN,
                  L"SELECT Name,PNPDeviceID FROM Win32_NetworkAdapter WHERE "
                  L"Index=%d",
                  vt.uintVal);
        wpa_printf(MSG_DEBUG, "ndis_events: WMI: %S", query);
        VariantClear(&vt);
        IWbemClassObject_Release(pObj);

        hr = call_IWbemServices_ExecQuery(
                pSvc, L"WQL", query,
                WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
                NULL, &pEnumerator);
        if (!SUCCEEDED(hr)) {
                wpa_printf(MSG_DEBUG, "ndis_events: Failed to query interface "
                           "from Win32_NetworkAdapter: 0x%x", (int) hr);
                IWbemServices_Release(pSvc);
                return ndis_events_use_desc(events, desc);
        }

        uReturned = 0;
        hr = IEnumWbemClassObject_Next(pEnumerator, WBEM_INFINITE, 1,
                                       &pObj, &uReturned);
        if (!SUCCEEDED(hr) || uReturned == 0) {
                wpa_printf(MSG_DEBUG, "ndis_events: Failed to find interface "
                           "from Win32_NetworkAdapter: 0x%x", (int) hr);
                IEnumWbemClassObject_Release(pEnumerator);
                IWbemServices_Release(pSvc);
                return ndis_events_use_desc(events, desc);
        }
        IEnumWbemClassObject_Release(pEnumerator);

        hr = IWbemClassObject_Get(pObj, L"Name", 0, &vt, NULL, NULL);
        if (!SUCCEEDED(hr)) {
                wpa_printf(MSG_DEBUG, "ndis_events: Failed to get Name from "
                           "Win32_NetworkAdapter: 0x%x", (int) hr);
                IWbemClassObject_Release(pObj);
                IWbemServices_Release(pSvc);
                return ndis_events_use_desc(events, desc);
        }

        wpa_printf(MSG_DEBUG, "ndis_events: Win32_NetworkAdapter::Name='%S'",
                   vt.bstrVal);
        events->adapter_desc = _wcsdup(vt.bstrVal);
        VariantClear(&vt);

        /*
         * Try to get even better candidate for matching with InstanceName
         * from Win32_PnPEntity. This is needed at least for some USB cards
         * that can change the InstanceName whenever being unplugged and
         * plugged again.
         */

        hr = IWbemClassObject_Get(pObj, L"PNPDeviceID", 0, &vt, NULL, NULL);
        if (!SUCCEEDED(hr)) {
                wpa_printf(MSG_DEBUG, "ndis_events: Failed to get PNPDeviceID "
                           "from Win32_NetworkAdapter: 0x%x", (int) hr);
                IWbemClassObject_Release(pObj);
                IWbemServices_Release(pSvc);
                if (events->adapter_desc == NULL)
                        return ndis_events_use_desc(events, desc);
                return 0; /* use Win32_NetworkAdapter::Name */
        }

        wpa_printf(MSG_DEBUG, "ndis_events: Win32_NetworkAdapter::PNPDeviceID="
                   "'%S'", vt.bstrVal);

        len = _snwprintf(query, MAX_QUERY_LEN,
                        L"SELECT Name FROM Win32_PnPEntity WHERE DeviceID='");
        if (len < 0 || len >= MAX_QUERY_LEN - 1) {
                VariantClear(&vt);
                IWbemClassObject_Release(pObj);
                IWbemServices_Release(pSvc);
                if (events->adapter_desc == NULL)
                        return ndis_events_use_desc(events, desc);
                return 0; /* use Win32_NetworkAdapter::Name */
        }

        /* Escape \ as \\ */
        for (pos = 0; vt.bstrVal[pos] && len < MAX_QUERY_LEN - 2; pos++) {
                if (vt.bstrVal[pos] == '\\') {
                        if (len >= MAX_QUERY_LEN - 3)
                                break;
                        query[len++] = '\\';
                }
                query[len++] = vt.bstrVal[pos];
        }
        query[len++] = L'\'';
        query[len] = L'\0';
        VariantClear(&vt);
        IWbemClassObject_Release(pObj);
        wpa_printf(MSG_DEBUG, "ndis_events: WMI: %S", query);

        hr = call_IWbemServices_ExecQuery(
                pSvc, L"WQL", query,
                WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
                NULL, &pEnumerator);
        if (!SUCCEEDED(hr)) {
                wpa_printf(MSG_DEBUG, "ndis_events: Failed to query interface "
                           "Name from Win32_PnPEntity: 0x%x", (int) hr);
                IWbemServices_Release(pSvc);
                if (events->adapter_desc == NULL)
                        return ndis_events_use_desc(events, desc);
                return 0; /* use Win32_NetworkAdapter::Name */
        }

        uReturned = 0;
        hr = IEnumWbemClassObject_Next(pEnumerator, WBEM_INFINITE, 1,
                                       &pObj, &uReturned);
        if (!SUCCEEDED(hr) || uReturned == 0) {
                wpa_printf(MSG_DEBUG, "ndis_events: Failed to find interface "
                           "from Win32_PnPEntity: 0x%x", (int) hr);
                IEnumWbemClassObject_Release(pEnumerator);
                IWbemServices_Release(pSvc);
                if (events->adapter_desc == NULL)
                        return ndis_events_use_desc(events, desc);
                return 0; /* use Win32_NetworkAdapter::Name */
        }
        IEnumWbemClassObject_Release(pEnumerator);

        hr = IWbemClassObject_Get(pObj, L"Name", 0, &vt, NULL, NULL);
        if (!SUCCEEDED(hr)) {
                wpa_printf(MSG_DEBUG, "ndis_events: Failed to get Name from "
                           "Win32_PnPEntity: 0x%x", (int) hr);
                IWbemClassObject_Release(pObj);
                IWbemServices_Release(pSvc);
                if (events->adapter_desc == NULL)
                        return ndis_events_use_desc(events, desc);
                return 0; /* use Win32_NetworkAdapter::Name */
        }

        wpa_printf(MSG_DEBUG, "ndis_events: Win32_PnPEntity::Name='%S'",
                   vt.bstrVal);
        os_free(events->adapter_desc);
        events->adapter_desc = _wcsdup(vt.bstrVal);
        VariantClear(&vt);

        IWbemClassObject_Release(pObj);

        IWbemServices_Release(pSvc);

        if (events->adapter_desc == NULL)
                return ndis_events_use_desc(events, desc);

        return 0;
}


struct ndis_events_data *
ndis_events_init(HANDLE *read_pipe, HANDLE *event_avail,
                 const char *ifname, const char *desc)
{
        HRESULT hr;
        IWbemObjectSink *pSink;
        struct ndis_events_data *events;

        events = os_zalloc(sizeof(*events));
        if (events == NULL) {
                wpa_printf(MSG_ERROR, "Could not allocate sink for events.");
                return NULL;
        }
        events->ifname = os_strdup(ifname);
        if (events->ifname == NULL) {
                os_free(events);
                return NULL;
        }

        if (wmi_refcnt++ == 0) {
                hr = CoInitializeEx(0, COINIT_MULTITHREADED);
                if (FAILED(hr)) {
                        wpa_printf(MSG_ERROR, "CoInitializeEx() failed - "
                                   "returned 0x%x", (int) hr);
                        os_free(events);
                        return NULL;
                }
        }

        if (wmi_first) {
                /* CoInitializeSecurity() must be called once and only once
                 * per process, so let's use wmi_first flag to protect against
                 * multiple calls. */
                wmi_first = 0;

                hr = CoInitializeSecurity(NULL, -1, NULL, NULL,
                                          RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
                                          RPC_C_IMP_LEVEL_IMPERSONATE,
                                          NULL, EOAC_SECURE_REFS, NULL);
                if (FAILED(hr)) {
                        wpa_printf(MSG_ERROR, "CoInitializeSecurity() failed "
                                   "- returned 0x%x", (int) hr);
                        os_free(events);
                        return NULL;
                }
        }

        hr = CoCreateInstance(&CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER,
                              &IID_IWbemLocator,
                              (LPVOID *) (void *) &events->pLoc);
        if (FAILED(hr)) {
                wpa_printf(MSG_ERROR, "CoCreateInstance() failed - returned "
                           "0x%x", (int) hr);
                CoUninitialize();
                os_free(events);
                return NULL;
        }

        if (ndis_events_get_adapter(events, ifname, desc) < 0) {
                CoUninitialize();
                os_free(events);
                return NULL;
        }
        wpa_printf(MSG_DEBUG, "ndis_events: use adapter descriptor '%S'",
                   events->adapter_desc);

        hr = call_IWbemLocator_ConnectServer(
                events->pLoc, L"ROOT\\WMI", NULL, NULL,
                0, 0, 0, 0, &events->pSvc);
        if (FAILED(hr)) {
                wpa_printf(MSG_ERROR, "Could not connect to server - error "
                           "0x%x", (int) hr);
                CoUninitialize();
                os_free(events->adapter_desc);
                os_free(events);
                return NULL;
        }
        wpa_printf(MSG_DEBUG, "Connected to ROOT\\WMI.");

        ndis_events_constructor(events);
        pSink = &events->sink;
        pSink->lpVtbl = &events->sink_vtbl;
        events->sink_vtbl.QueryInterface = ndis_events_query_interface;
        events->sink_vtbl.AddRef = ndis_events_add_ref;
        events->sink_vtbl.Release = ndis_events_release;
        events->sink_vtbl.Indicate = ndis_events_indicate;
        events->sink_vtbl.SetStatus = ndis_events_set_status;

        if (register_async_notification(pSink, events->pSvc) < 0) {
                wpa_printf(MSG_DEBUG, "Failed to register async "
                           "notifications");
                ndis_events_destructor(events);
                os_free(events->adapter_desc);
                os_free(events);
                return NULL;
        }

        *read_pipe = events->read_pipe;
        *event_avail = events->event_avail;

        return events;
}
